support kml 2.3 track element. (#264)
authortsteven4 <tsteven4@users.noreply.github.com>
Fri, 2 Nov 2018 13:55:31 +0000 (07:55 -0600)
committerGitHub <noreply@github.com>
Fri, 2 Nov 2018 13:55:31 +0000 (07:55 -0600)
* support kml 2.3 track element.

relax ordering requirement for gx:Track when and
coord children.

* fix kml includes and indentation.

kml.cc
reference/track/Placemark-Track-1.kml [new file with mode: 0644]
reference/track/Placemark-Track-1~kml.gpx [new file with mode: 0644]
testo.d/kml.test

diff --git a/kml.cc b/kml.cc
index d0d1859ca28277b0cb12e20ebaf1def1dafb84fc..43858f339f2420f5eb0129dc185aa17d15c23380 100644 (file)
--- a/kml.cc
+++ b/kml.cc
 
 #include "defs.h"
 #include "grtcirc.h"
+#include "queue.h"
+#include "src/core/datetime.h"
 #include "src/core/file.h"
 #include "src/core/xmlstreamwriter.h"
 #include "src/core/xmltag.h"
 #include "xmlgeneric.h"
-#include <QtCore/QRegExp>
-#include <QtCore/QXmlStreamAttributes>
+#include <QtCore/QXmlStreamAttributes> // for QXmlStreamAttributes
+#include <QtCore/QDateTime>            // for QDateTime
+#include <QtCore/QFile>                // for QFile
+#include <QtCore/QList>                // for QList
+#include <QtCore/QString>              // for QString, QStringLiteral, QStat...
+#include <QtCore/QtGlobal>             // for qint64, qPrintable
+#include <cctype>
 #include <cmath>
 #include <cstdio>
 #include <cstdlib>
+#include <cstring>
+#include <tuple>
 
 // options
 static char* opt_deficon = nullptr;
@@ -73,6 +82,7 @@ static QString posnfilenametmp;
 
 static route_head* gx_trk_head;
 static QList<gpsbabel::DateTime>* gx_trk_times;
+static QList<std::tuple<int, double, double, double>>* gx_trk_coords;
 
 static gpsbabel::File* oqfile;
 static gpsbabel::XmlStreamWriter* writer;
@@ -276,6 +286,14 @@ xg_tag_mapping kml_map[] = {
   { gx_trk_e,          cb_end,         "/Placemark/*gx:Track" },
   { gx_trk_when,  cb_cdata, "/Placemark/*gx:Track/when" },
   { gx_trk_coord, cb_cdata, "/Placemark/*gx:Track/gx:coord" },
+  { gx_trk_s,          cb_start,       "/Placemark/Track" }, // KML 2.3
+  { gx_trk_e,          cb_end,         "/Placemark/Track" }, // KML 2.3
+  { gx_trk_when,  cb_cdata, "/Placemark/Track/when" }, // KML 2.3
+  { gx_trk_coord, cb_cdata, "/Placemark/Track/coord" }, // KML 2.3
+  { gx_trk_s,          cb_start,       "/Placemark/MultiTrack/Track" }, // KML 2.3
+  { gx_trk_e,          cb_end,         "/Placemark/MultiTrack/Track" }, // KML 2.3
+  { gx_trk_when,  cb_cdata, "/Placemark/MultiTrack/Track/when" }, // KML 2.3
+  { gx_trk_coord, cb_cdata, "/Placemark/MultiTrack/Track/coord" }, // KML 2.3
   { nullptr,   (xg_cb_type) 0,                 nullptr }
 };
 
@@ -345,12 +363,12 @@ void wpt_time(xg_string args, const QXmlStreamAttributes*)
 
 void wpt_ts_begin(xg_string args, const QXmlStreamAttributes*)
 {
-       wpt_timespan_begin = xml_parse_time(args);
+  wpt_timespan_begin = xml_parse_time(args);
 }
 
 void wpt_ts_end(xg_string args, const QXmlStreamAttributes*)
 {
-       wpt_timespan_end = xml_parse_time(args);
+  wpt_timespan_end = xml_parse_time(args);
 }
 
 void wpt_coord(const QString& args, const QXmlStreamAttributes*)
@@ -419,20 +437,20 @@ void trk_coord(xg_string args, const QXmlStreamAttributes*)
    *
    * If this is specified, then SetCreationDate
    */
-  if ( wpt_timespan_begin.isValid() && wpt_timespan_end.isValid() ) {
-
-         // If there are some Waypoints, then distribute the TimeSpan to all Waypoints
-         if ( trk_head->rte_waypt_ct > 0 ) {
-                 qint64 timespan_ms = wpt_timespan_begin.msecsTo(wpt_timespan_end);
-                 qint64 ms_per_waypoint = timespan_ms / trk_head->rte_waypt_ct;
-                  queue* curr_elem;
-                  queue* tmp_elem;
-                 QUEUE_FOR_EACH(&trk_head->waypoint_list, curr_elem, tmp_elem) {
-                         trkpt = reinterpret_cast<Waypoint *>(curr_elem);
-                         trkpt->SetCreationTime(wpt_timespan_begin);
-                         wpt_timespan_begin = wpt_timespan_begin.addMSecs(ms_per_waypoint);
-                 }
-         }
+  if (wpt_timespan_begin.isValid() && wpt_timespan_end.isValid()) {
+
+    // If there are some Waypoints, then distribute the TimeSpan to all Waypoints
+    if (trk_head->rte_waypt_ct > 0) {
+      qint64 timespan_ms = wpt_timespan_begin.msecsTo(wpt_timespan_end);
+      qint64 ms_per_waypoint = timespan_ms / trk_head->rte_waypt_ct;
+      queue* curr_elem;
+      queue* tmp_elem;
+      QUEUE_FOR_EACH(&trk_head->waypoint_list, curr_elem, tmp_elem) {
+        trkpt = reinterpret_cast<Waypoint*>(curr_elem);
+        trkpt->SetCreationTime(wpt_timespan_begin);
+        wpt_timespan_begin = wpt_timespan_begin.addMSecs(ms_per_waypoint);
+      }
+    }
   }
 }
 
@@ -448,15 +466,51 @@ void gx_trk_s(xg_string, const QXmlStreamAttributes*)
   track_add_head(gx_trk_head);
   delete gx_trk_times;
   gx_trk_times = new QList<gpsbabel::DateTime>;
+  delete gx_trk_coords;
+  gx_trk_coords = new QList<std::tuple<int, double, double, double>>;
 }
 
 void gx_trk_e(xg_string, const QXmlStreamAttributes*)
 {
+  // Check that for every temporal value (kml:when) in a kml:Track there is a position (kml:coord) value.
+  // Check that for every temporal value (kml:when) in a gx:Track there is a position (gx:coord) value.
+  if (gx_trk_times->size() != gx_trk_coords->size()) {
+    fatal(MYNAME ": There were more coord elements than the number of when elements.\n");
+  }
+
+  // In KML 2.3 kml:Track elements kml:coord and kml:when elements are not required to be in any order.
+  // In gx:Track elements all kml:when elements are required to precede all gx:coord elements.
+  // For both we allow any order.  Many writers using gx:Track elements don't adhere to the schema.
+  while (!gx_trk_times->isEmpty()) {
+    Waypoint* trkpt = new Waypoint;
+    trkpt->SetCreationTime(gx_trk_times->takeFirst());
+    double lat, lon, alt;
+    int n;
+    std::tie(n, lat, lon, alt) = gx_trk_coords->takeFirst();
+    // An empty kml:coord element is permitted to indicate missing position data;
+    // the estimated position may be determined using some interpolation method.
+    // However if we get one we will throw away the time as we don't have a location.
+    // It is not clear that coord elements without altitude are allowed, but our
+    // writer produces them.
+    if (n >= 2) {
+      trkpt->latitude = lat;
+      trkpt->longitude = lon;
+      if (n >= 3) {
+        trkpt->altitude = alt;
+      }
+      track_add_wpt(gx_trk_head, trkpt);
+    } else {
+      delete trkpt;
+    }
+  }
+
   if (!gx_trk_head->rte_waypt_ct) {
     track_del_head(gx_trk_head);
   }
   delete gx_trk_times;
   gx_trk_times = nullptr;
+  delete gx_trk_coords;
+  gx_trk_coords = nullptr;
 }
 
 void gx_trk_when(xg_string args, const QXmlStreamAttributes*)
@@ -469,31 +523,16 @@ void gx_trk_when(xg_string args, const QXmlStreamAttributes*)
 
 void gx_trk_coord(xg_string args, const QXmlStreamAttributes*)
 {
-  double lat, lon, alt;
-
-  if (! gx_trk_times || gx_trk_times->isEmpty()) {
-    fatal(MYNAME ": There were more gx:coord elements than the number of when elements.\n");
+  if (! gx_trk_coords) {
+    fatal(MYNAME ": gx_trk_coord: invalid kml file\n");
   }
-  Waypoint* trkpt = new Waypoint;
-  trkpt->SetCreationTime(gx_trk_times->takeFirst());
+
+  double lat, lon, alt;
   int n = sscanf(CSTR(args), "%lf %lf %lf", &lon, &lat, &alt);
-  // Empty gx_coord elements are allowed to balance the number of when elements,
-  // but if we get one we will throw away the time as we don't have a location.
-  // It is not clear that coord elements without altitude are allowed, but our
-  // writer produces them.
   if (0 != n && 2 != n && 3 != n) {
-    fatal(MYNAME ": gx:coord field decode failure on \"%s\".\n", qPrintable(args));
-  }
-  if (n >= 2) {
-    trkpt->latitude = lat;
-    trkpt->longitude = lon;
-    if (n >= 3) {
-      trkpt->altitude = alt;
-    }
-    track_add_wpt(gx_trk_head, trkpt);
-  } else {
-    delete trkpt;
+    fatal(MYNAME ": coord field decode failure on \"%s\".\n", qPrintable(args));
   }
+  gx_trk_coords->append(std::make_tuple(n, lat, lon, alt));
 }
 
 static
@@ -1059,7 +1098,7 @@ static void kml_output_tailer(const route_head* header)
     queue* elem, *tmp;
 
     QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) {
-      Waypoint* tpt = reinterpret_cast<Waypoint *>(elem);
+      Waypoint* tpt = reinterpret_cast<Waypoint*>(elem);
       int first_in_trk = tpt->Q.prev == &header->waypoint_list;
       if (!first_in_trk && tpt->wpt_flags.new_trkseg) {
         needs_multigeometry = 1;
@@ -1096,7 +1135,7 @@ static void kml_output_tailer(const route_head* header)
     }
 
     QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) {
-      Waypoint* tpt = reinterpret_cast<Waypoint *>(elem);
+      Waypoint* tpt = reinterpret_cast<Waypoint*>(elem);
       int first_in_trk = tpt->Q.prev == &header->waypoint_list;
       if (tpt->wpt_flags.new_trkseg) {
         if (!first_in_trk) {
@@ -1684,7 +1723,7 @@ static void kml_mt_simple_array(const route_head* header,
 
   QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) {
 
-    Waypoint* wpt = reinterpret_cast<Waypoint *>(elem);
+    Waypoint* wpt = reinterpret_cast<Waypoint*>(elem);
 
     switch (member) {
     case fld_power:
@@ -1715,7 +1754,7 @@ static int track_has_time(const route_head* header)
   queue* elem, *tmp;
   int points_with_time = 0;
   QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) {
-    Waypoint* tpt = reinterpret_cast<Waypoint *>(elem);
+    Waypoint* tpt = reinterpret_cast<Waypoint*>(elem);
 
     if (tpt->GetCreationTime().isValid()) {
       points_with_time++;
@@ -1733,7 +1772,7 @@ static void write_as_linestring(const route_head* header)
   queue* elem, *tmp;
   kml_track_hdr(header);
   QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) {
-    Waypoint* tpt = reinterpret_cast<Waypoint *>(elem);
+    Waypoint* tpt = reinterpret_cast<Waypoint*>(elem);
     kml_track_disp(tpt);
   }
   kml_track_tlr(header);
@@ -1763,7 +1802,7 @@ static void kml_mt_hdr(const route_head* header)
   kml_output_positioning(false);
 
   QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) {
-    Waypoint* tpt = reinterpret_cast<Waypoint *>(elem);
+    Waypoint* tpt = reinterpret_cast<Waypoint*>(elem);
 
     if (tpt->GetCreationTime().isValid()) {
       QString time_string = tpt->CreationTimeXML();
@@ -1776,7 +1815,7 @@ static void kml_mt_hdr(const route_head* header)
 
   // TODO: How to handle clamped, floating, extruded, etc.?
   QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) {
-    Waypoint* tpt = reinterpret_cast<Waypoint *>(elem);
+    Waypoint* tpt = reinterpret_cast<Waypoint*>(elem);
 
     if (kml_altitude_known(tpt)) {
       writer->writeTextElement(QStringLiteral("gx:coord"),
@@ -2153,7 +2192,7 @@ kml_wr_position(Waypoint* wpt)
      track points if we've not moved a minimum distance from the
      beginning of our accumulated track. */
   {
-    Waypoint* newest_posn= reinterpret_cast<Waypoint *>QUEUE_LAST(&posn_trk_head->waypoint_list);
+    Waypoint* newest_posn= reinterpret_cast<Waypoint*>QUEUE_LAST(&posn_trk_head->waypoint_list);
 
     if (radtometers(gcdist(RAD(wpt->latitude), RAD(wpt->longitude),
                            RAD(newest_posn->latitude), RAD(newest_posn->longitude))) > 50) {
@@ -2178,7 +2217,7 @@ kml_wr_position(Waypoint* wpt)
    */
   while (max_position_points &&
          (posn_trk_head->rte_waypt_ct >= max_position_points)) {
-    Waypoint* tonuke = reinterpret_cast<Waypoint *>QUEUE_FIRST(&posn_trk_head->waypoint_list);
+    Waypoint* tonuke = reinterpret_cast<Waypoint*>QUEUE_FIRST(&posn_trk_head->waypoint_list);
     track_del_wpt(posn_trk_head, tonuke);
   }
 
diff --git a/reference/track/Placemark-Track-1.kml b/reference/track/Placemark-Track-1.kml
new file mode 100644 (file)
index 0000000..85cd1b0
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<kml xmlns="http://www.opengis.net/kml/2.2" version="2.3">
+       <Placemark id="PM005">
+               <Track>
+                       <when>2010-05-28T02:02:09Z</when>
+                       <when>2010-05-28T02:02:35Z</when>
+                       <when>2010-05-28T02:02:44Z</when>
+                       <when>2010-05-28T02:02:53Z</when>
+                       <when>2010-05-28T02:02:54Z</when>
+                       <when>2010-05-28T02:02:55Z</when>
+                       <when>2010-05-28T02:02:56Z</when>
+                       <coord>-122.207881 37.371915 156.000000</coord>
+                       <coord>-122.205712 37.373288 152.000000</coord>
+                       <coord>-122.204678 37.373939 147.000000</coord>
+                       <coord>-122.203572 37.374630 142.199997</coord>
+                       <coord>-122.203451 37.374706 141.800003</coord>
+                       <coord>-122.203329 37.374780 141.199997</coord>
+                       <coord>-122.203207 37.374857 140.199997</coord>
+               </Track>
+       </Placemark>
+</kml>
diff --git a/reference/track/Placemark-Track-1~kml.gpx b/reference/track/Placemark-Track-1~kml.gpx
new file mode 100644 (file)
index 0000000..7862726
--- /dev/null
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gpx version="1.0" creator="GPSBabel - http://www.gpsbabel.org" xmlns="http://www.topografix.com/GPX/1/0">
+  <time>1970-01-01T00:00:00Z</time>
+  <bounds minlat="37.371915000" minlon="-122.207881000" maxlat="37.374857000" maxlon="-122.203207000"/>
+  <trk>
+    <trkseg>
+      <trkpt lat="37.371915000" lon="-122.207881000">
+        <ele>156.000</ele>
+        <time>2010-05-28T02:02:09Z</time>
+      </trkpt>
+      <trkpt lat="37.373288000" lon="-122.205712000">
+        <ele>152.000</ele>
+        <time>2010-05-28T02:02:35Z</time>
+      </trkpt>
+      <trkpt lat="37.373939000" lon="-122.204678000">
+        <ele>147.000</ele>
+        <time>2010-05-28T02:02:44Z</time>
+      </trkpt>
+      <trkpt lat="37.374630000" lon="-122.203572000">
+        <ele>142.200</ele>
+        <time>2010-05-28T02:02:53Z</time>
+      </trkpt>
+      <trkpt lat="37.374706000" lon="-122.203451000">
+        <ele>141.800</ele>
+        <time>2010-05-28T02:02:54Z</time>
+      </trkpt>
+      <trkpt lat="37.374780000" lon="-122.203329000">
+        <ele>141.200</ele>
+        <time>2010-05-28T02:02:55Z</time>
+      </trkpt>
+      <trkpt lat="37.374857000" lon="-122.203207000">
+        <ele>140.200</ele>
+        <time>2010-05-28T02:02:56Z</time>
+      </trkpt>
+    </trkseg>
+  </trk>
+</gpx>
index 77d64ac20bee34c3daae10b24e37049b85a9ff20..55b3c743437f54f50db1c37672414090cd49a741 100644 (file)
@@ -61,6 +61,10 @@ compare ${REFERENCE}/bounds-test.kml ${TMPDIR}/bnds_so.kml
 gpsbabel -i gpx -f ${REFERENCE}/track/tracks.gpx -o kml,track=1,trackdirection=1,units='m' -F ${TMPDIR}/tracks~gpx.kml
 compare ${REFERENCE}/track/tracks~gpx.kml ${TMPDIR}/tracks~gpx.kml
 
+# kml 2.3
+gpsbabel -i kml -f ${REFERENCE}/track/Placemark-Track-1.kml -o gpx -F ${TMPDIR}/Placemark-Track-1~kml.gpx
+compare ${REFERENCE}/track/Placemark-Track-1~kml.gpx ${TMPDIR}/Placemark-Track-1~kml.gpx
+
 set -e
 if which xmllint > /dev/null;
 then